package com.fmy.test.config.dynamic;

import com.fmy.test.config.dynamic.algorithm.DBAlgorithm;
import com.fmy.test.config.dynamic.enums.DBType;
import com.fmy.test.config.dynamic.handler.DynamicHandler;
import com.fmy.test.config.dynamic.holder.DynamicContextHolder;
import com.fmy.test.config.dynamic.holder.DynamicHolderModel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.text.MessageFormat;

/**
 * @Auther: Mr.fmy
 * @Date: 2019/8/24 0024 17:14
 * @Description: <p>
 * 数据源切面类
 * </p>
 */
@Aspect
public class DBDynamicAspect implements Ordered {

    @Autowired
    private DynamicHandler dynamicHandler;

    @Autowired
    private DBAlgorithm algorithm;

    private final static Log log = LogFactory.getLog(DBDynamicAspect.class);

    //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
    @Pointcut("execution(* com.fmy.test.modular.service..*(..))")
    public void aspect() {
    }

    /*
     * 配置前置通知,使用在方法aspect()上注册的切入点
     * 同时接受JoinPoint切入点对象,可以没有该参数
     */
    @Around("aspect()")
    public Object before(ProceedingJoinPoint joinPoint) {
        boolean switchDb = false;
        try {
            DBType dbType = dynamicHandler.dynamicHolder(joinPoint.getSignature().getName());
            //1、验证是否需要切换数据源
            switchDb = switchDb(joinPoint);
            //2、如果需要、那么设置数据源
            if (switchDb) setDb(dbType);
            //3、验证数据源使用是否合法
            vailDBSpecs(dbType, joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
            //4、执行方法
            return joinPoint.proceed();
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
        } finally {
            //如果需要 那么将移除当前线程数据源
            if (switchDb) {
                DynamicContextHolder.remove();
            }
        }
        return null;
    }

    @Override
    public int getOrder() {
        return 1;
    }

    /**
     * 是否需要切换数据源
     * <p>
     * 如果当前线程存在数据源、将不切换
     * </p>
     */
    public boolean switchDb(ProceedingJoinPoint joinPoint) {
        //1、获取当前线程数据源
        DynamicHolderModel holderModel = DynamicContextHolder.get();
        //3、如果当前线程存在数据源、那么数据源将不进行切换
        return holderModel == null;
    }

    /**
     * 切换数据源
     *
     * @param dbType 数据源类型
     */
    public void setDb(DBType dbType) {
        //根据数据源类型利用 算法选择所使用的数据源
        String db = algorithm.getDB(dbType);
        DynamicContextHolder.set(new DynamicHolderModel(db, dbType));
        if (log.isInfoEnabled()) {
            log.info("当前数据源：" + db);
        }
    }

    /**
     * 验证数据源是否使用规范
     * <p>
     * 验证规范  master 为当前线程所使用DB类型
     * 1：master 如果为读类型 current 不可为写类型
     * </p>
     *
     * @param current 当前方法所使用的DB类型
     */
    public void vailDBSpecs(DBType current, Class c, String methodName) {
        DynamicHolderModel holderModel = DynamicContextHolder.get();
        if (holderModel.getDbType().equals(DBType.READ_DB) && current.equals(DBType.WRITE_DB)) {
            String errMsg = MessageFormat.format("数据源使用检测异常，方法：{0}.{1} 可能存在异常调用者、请排查", c.getName(), methodName);
            throw new IllegalArgumentException(errMsg);
        }
    }
}
